/*
* Fastjs official plugin
*
* About this plugin:
*   Version: v0.0.1
*   Plugin: app.fs.js
*   Author: Fastjs Team
*   Contact-Us: xiaodong@indouyin.cn
*/

class app {
    constructor(config) {
        if (typeof config !== "object") {
            fastjs.throwCrashError("app.config", "config", "type")
            return void 0
        }

        this.$config = {
            mount: [],
            prop: {
                name: [],
                val: [],
                list: {},
                dom: [],
                target: {}
            },
            var: {
                el: [],
                list: []
            },
            obj: {
                html: [],
                text: []
            },
            hook: {
                beforeCreate: null,
                created: null,
                beforeMount: null,
                mounted: null,
                beforeUpdate: null,
                updated: null,
                beforeDestroy: null,
                destroyed: null,
                setup: null
            }
        }
        // hook setup
        if (typeof config.beforeCreate === "function") {
            this.$config.hook.beforeCreate = config.beforeCreate
        }
        if (typeof config.created === "function") {
            this.$config.hook.created = config.created
        }
        if (typeof config.beforeMount === "function") {
            this.$config.hook.beforeMount = config.beforeMount
        }
        if (typeof config.mounted === "function") {
            this.$config.hook.mounted = config.mounted
        }
        if (typeof config.beforeUpdate === "function") {
            this.$config.hook.beforeUpdate = config.beforeUpdate
        }
        if (typeof config.updated === "function") {
            this.$config.hook.updated = config.updated
        }
        if (typeof config.beforeDestroy === "function") {
            this.$config.hook.beforeDestroy = config.beforeDestroy
        }
        if (typeof config.destroyed === "function") {
            this.$config.hook.destroyed = config.destroyed
        }
        if (typeof config.data !== "function") {
            fastjs.throwCrashError("app.config.data()", "You should have a data() function to setup fastjs app")
            return void 0
        }
        this.$debug("__hook__.beforeCreate", "Running beforeCreate hook")
        if (this.$config.hook.beforeCreate !== null) {
            this.$config.hook.beforeCreate()
        }
        this.$config.hook.setup = config.data
        let data = this.$config.hook.setup()
        if (typeof data !== "object") {
            fastjs.throwCrashError("app.config.data().return_statement", "function data() should return a object")
            return void 0
        }
        Object.entries(data).forEach((e) => {
            this.$debug("__constructor__.config.readline", "Read a line of %s%:%s%".push([e[0], e[1]], "%s%"))
            this.$config.prop.name.push(e[0])
            this.$config.prop.val.push(e[1])
            this.$config.prop.list[e[0]] = e[1]
            this.$config.prop.target[e[0]] = []
            this.$config.obj.html[e[0]] = []
            this.$config.obj.text[e[0]] = []
        })
        data.__this__ = this
        this.$debug("__constructor__.config.data.list.length", Object.entries(data).length)
        this.data = new Proxy(data, {
            set: function (target, prop, value) {
                target.__this__.$debug("__proxy__.listen::%s%.changeTo".push(prop, "%s%"), value)
                target.__this__.$proxy(prop, value)
                target[prop] = value
                return true
            }
        })
        this.$config.hook.data = this.data
        this.$debug("__proxy__.setup", "already setup proxy to listen data")
        this.$debug("__hook__.created", "Running created hook")
        if (this.$config.hook.created !== null) {
            this.$config.hook.created()
        }
        return this
    }

    mount(selecter) {
        this.$debug("__hook__.beforeMount", "Running beforeMount hook")
        if (typeof this.$config.hook.beforeMount === "function") {
            this.$config.hook.beforeMount()
        }
        let el = document.querySelector(selecter)
        if (!el) {
            fastjs.throwCrashError("app.mount", "target dom is empty")
            return void 0
        }
        let target = this.$next(el, "f-html")
        target.forEach((e) => {
            let val = e.attr("f-html", false)
            let vals = val.match(/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/g)
            if (typeof vals === "object") {
                vals.forEach((str) => {
                    if (this.$config.prop.name.search(str) === false) {
                        fastjs.throwCrashError("__mount__.f-html", "variable %s% is not defined, you should return in data()".push(str, "%s%"))
                        return void 0
                    }
                    this.$config.obj.html[str].push(e)
                    e.html(String(this.$config.prop.list[str]))
                })
            }
            e.attr("f-html", null)
        })
        this.$debug("__mount__.f-html", "Found %s% target in element".push(target.length, "%s%"))
        target = this.$next(el, "f-text")
        target.forEach((e) => {
            let val = e.attr("f-text", false)
            let vals = val.match(/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/g)
            if (typeof vals === "object") {
                vals.forEach((str) => {
                    if (this.$config.prop.name.search(str) === false) {
                        fastjs.throwCrashError("__mount__.f-text", "variable %s% is not defined, you should return in data()".push(str, "%s%"))
                        return void 0
                    }
                    this.$config.obj.text[str].push(e)
                    e.text(this.$config.prop.list[str])
                })
            }
        })
        this.$debug("__mount__.f-text", "Found %s% target in element".push(target.length, "%s%"))
        let _el = el.gethtml().match(/{{ *[a-zA-Z_$][a-zA-Z0-9_$.]* *}}/g)
        if (_el === null) {
            _el = []
        }

        let els = _el.length
        _el.forEach((e) => {
            this.$debug("__mount__", "Found %s% target in element".push(els, "%s%"))
            let name = e.match(/[a-zA-Z_$][a-zA-Z0-9_$.]*/g)[0]
            let data = this.$variable(this.data, name)
            el.html(el.gethtml().replace(e, `<span fastjs-setup="0"></span>`))
            let target = el.next("span[fastjs-setup='0']")[0]
            target.attr("fastjs-setup", null).html(String(data))
            let _target = this.$config.prop.target
            name.array(".").forEach((e, key) => {
                if (key + 1 !== name.length) {
                    _target[e] = []
                } else {
                    _target[e] = {}
                }
                _target = _target[e]
            })
            _target.push(target)
        })
        this.$config.mount.push(el)
        this.$debug("__hook__.mounted", "Running mounted hook")
        if (typeof this.$config.hook.mounted === "function") {
            this.$config.hook.mounted()
        }
        return this
    }

    $proxy(prop, value) {
        if (this.$config.prop.name.search(prop) === false) {
            fastjs.throwCrashError("__proxy__.listen::%s%".push(prop, "%s%"), "You should return this variable in data()")
            return void 0
        }
        this.$debug("__hook__.beforeUpdate", "Running beforeUpdate hook")
        if (typeof this.$config.hook.beforeUpdate === "function") {
            this.$config.hook.beforeUpdate(prop, value, this)
        }
        this.$config.prop.list[prop] = value
        this.$config.prop.val[this.$config.prop.name.search(prop)] = value
        this.$config.prop.target[prop].forEach((el) => {
            el.html(value)
        })
        this.$config.obj.html[prop].forEach((e) => {
            e.html(value)
        })
        this.$config.obj.text[prop].forEach((e) => {
            e.text(value)
        })
        this.$debug("__hook__.updated", "Running updated hook")
        if (typeof this.$config.hook.updated === "function") {
            this.$config.hook.updated(prop, value, this)
        }
        return true
    }

    $debug($place, $text) {
        if (FASTJS_CONFIG.debug.app) {
            let output = "[Fastjs Debug] Fastjs.app.%s%: %s%".push([$place, $text], "%s%")
            console.log(output)
        }
    }

    $next($el, $selecter) {
        let el = []
        if ($el.attr($selecter, false) !== null)
            el.push($el)
        $el.next("*[%s%]".push($selecter, "%s%")).forEach((e) => {
            el.push(e)
        })
        return el
    }

    $variable($data, $get) {
        let list = $get.array(".")
        let data = $data
        list.forEach((e) => {
            if (data[e] !== void 0) {
                data = data[e]
            } else {
                fastjs.throwCrashError("__mount__.%s%".push($get, "%s%"), "You should return this variable in data()")
            }
        })
        return data
    }

    destroy() {
        if (typeof this.$config.hook.beforeDestroy === "function")
            if (this.$config.hook.beforeDestroy() === false)
                return false
        let destroyed
        if (typeof this.$config.hook.destroyed === "function")
            destroyed = this.$config.hook.destroyed
        this.data = null
        this.$proxy = null
        this.mount = null
        this.$config = null
        if (typeof destroyed === "function") {
            destroyed()
        }
        return true
    }
}